All right.
I hope you're having fun.
So far in the realm of type annotation, in this lecture, we're going to take a look at generics.
What is a generic?
Well, a generic is actually a type parameter that can be replaced by another type during the runtime
of a script.
Think of how we can create parameters for functions, and those parameters can represent certain values
well.
We can also define parameters for types as well and override those parameters to represent a specific
type.
This is incredibly useful when we need to create some custom types, and we don't want to have to create
a new type for every single thing that could exist in, for example, a table like creating a type that
represents a table containing strings.
Then we have to create a type representing a table containing numbers, and so on.
So how do we create a generic or a type parameter.
Well I'm going to create a new type.
I'm going to call it array.
And before I do anything else I'm going to put a less than and then a greater than symbol.
And between these two symbols is where we can define type parameters.
For our custom type I can name the parameters whatever I'd like, just like I can with parameters and
functions.
So for simplicity's sake I'm going to name my parameter t t for type.
And then I can set this type equal to a table.
And since this table is going to represent an array arrays are ordered by numbers or indexes.
So I'll type in number here for the index.
And what's going to be stored at this index is going to be the type T.
I don't know what type T is going to be, but that's where we're going to use this type later on and
define what T is going to be during runtime of our script.
Now, I don't have to go and define types for every single type of arrays, like string arrays, number
arrays, and so on.
Instead, I can make a single type and represent whatever type is going to be stored here as a parameter.
And this is my generic.
So that means I can create an array like a string array and set it to the type of array.
And then I'll put the less than and greater than symbol.
And in here I can define the type.
So for example in this table I want to store strings.
So I'll say string.
And then I can set it equal to a table.
And in here I can store all my strings like hello and hi.
And as you can see we don't get any red underlines by Roblox Studio or what they call the linter.
So all I did is that I replaced our placeholder T with the type string.
Pretty neat.
We can also do the same thing with numbers.
So I can create a number array, set it to the type of array.
And this one is going to be a number.
And now I can set it to a table that contains numbers like one, two and three.
So as you can see it's basically no different than parameters we have in functions.
But it doesn't stop there.
In fact, we can define multiple type parameters for a custom type.
So as an example I'm going to create another new type.
I'm going to call it pair.
And in here I'm going to define two type placeholders.
I'm just going to call one T and I'll call the other one u.
And then I can set it equal to a table.
And let's say I want this table to have two key value pairs.
One will just be called first.
And this one's going to store the value of t.
And then I'll have another key value pair I'll call it second.
And this one's going to store the value of u.
And I'm going to create a variable.
I'll just call it sum pair.
And I'll set the type equal to our custom pair type.
And I'll override the first parameter with the type let's say string.
And then for the second type I can do number.
And now I can set it equal to a table.
And for the first key value pair I'll just store a string like.
Hello.
And then for the second key value pair, I'll just store a number like five.
And there we go.
We've overridden these two parameters here with string and number.
It's that simple.
Now generics actually even go beyond creating custom types.
We can actually use generics in functions too.
And they're called generic functions.
A generic function is when we define type parameters for the function itself that it can work with.
The purpose of doing this is to reduce the copying and pasting of our code by creating universal functions
that can work with multiple data types.
We don't have to define a function to work with each specific data type.
So to create a generic function, I'm going to create a function just like I regularly would.
I'm going to call this function in particular reverse.
And before I do anything else, I'm going to again put the less than and greater than symbol for this
function.
And in here we're going to define again a parameter for the type that this function is going to contain.
And I'm going to call it t.
Again for simplicity's sake.
And then we can put our parentheses.
And in here I want to let's say I want to pass an array to this function.
So I'll just call the parameter array.
And it's going to be of the type table.
And this table is going to contain types of type t.
So that's what our T is going to represent here.
And what I want to do is I want to take all the values in this array and reverse them.
So whatever is in the front is last and that kind of thing.
So I can return a table back from this function that also contains all of those types of type T.
So lots of teas here.
So basically what I'm going to do is I'll create a table, I'm going to call it reversed, set it to
the type of a table with tea inside of it, and then set it equal to an empty table.
And then what I could do is I could loop through every single value that is in my array table.
So I could do for I is equal to the number of elements in my array.
So we're going to start from the top.
And we're going to go to the last item which is at index one.
And that means we're going to have to decrement by one each time through this for loop.
So if I pass a table with five indexes in it, it'll start at five and go all the way down to one.
And then I can insert those values into my reverse table.
By referencing my array table and getting the value at that specific index.
And then at the very end I can just return this reverse table.
So if I pass an array that contain numbers like one, two, three and four, then it'll go start at
four and then it'll return to me a table that goes four, three, two, one.
So it's just reversing it.
And that means I don't have to explicitly define the types that are going to be stored in the array
that's passed to this function.
This array could consist of types of strings, could be maybe booleans numbers I don't know.
All I do know is that this function's main purpose is just to reverse whatever's in an array.
It doesn't really have to worry about the type, which is why we've set it to be generic.
This is a generic function, so now we can use this function to our advantage.
I can create a variable like number array.
And I can set it to be equal to a table that contains a bunch of numbers.
And it's going to be returned from the reverse function.
And then I can pass an array to the reverse function like one, two and three.
And this function will return back to me an array that has these numbers flipped.
And again I can do the same thing for strings.
I can have a string array, and we'll set it to a table that contains strings.
And we'll set it equal to the reverse function.
And I'll just pass another array to it that has strings like hi, we could do hello and howdy.
And it should return back to me a table that says howdy, hello and hi.
And we can actually confirm this by just printing out number array and then printing out string array
into the console.
So if we go and run the game, we should expect that our tables get flipped.
So if we go ahead and take a look here.
We see, we get three, two and one.
See our values have been flipped in order.
And then for the strings we get howdy, hello and hi instead of hi, hello and howdy.
And that's the beauty of our generic function in action.
But guess what?
Generic functions aren't also limited to only one type.
We can supply multiple different type parameters to our generic functions.
So let's make another example.
I'm going to create a new type.
I'll call this one pair two.
And this pair is going to have two type parameters.
I'll just call one t and we'll call the other one u.
And it's going to be equal to a table that contains, let's say the t is going to represent the key
for this table.
And then the value or the type of u is going to be stored at this key.
So we're going to have a nice little dictionary here.
And now using this custom type what I could do is I could create a function I'll call it map and I'll
define that this generic function will have a t and a u.
Again you can name these whatever you'd like.
I'm just doing t and u for simplicity's sake.
And this function can take an array that contains a bunch of values of type t.
And then this function will also require us to pass another function to it.
I'll just call it function or Funct.
And this function takes that type of t and it returns back to us the type of u.
So we want this function to take in t and return this u type.
And once this is finally done, we can say that this function returns a type of pair two using the t
and u that we've defined here for this function.
So this is looking a little confusing, but don't worry we're going to work through it.
So I'm going to create a variable called result.
Set it equal to pair two with the t and u parameters that we define and set it equal to an empty table
for now.
And then I can loop through every single value in the array that gets passed to this function.
So for every single value in Ipairs array, what I'll do is I will set the result table's key of v equal
to um, the function that gets passed the return from the function that gets passed to this map function.
So we'll do function and pass v to it.
So v is representing our type t.
And when we pass the type t to our function we should get returned back to us a type U which is being
stored in our table.
So again we're having a dictionary storing a bunch of keys with the type T with the values of type u,
and once that's done looping then we can return result back.
And I'll show you what we can do with this function.
I'll create another one.
I'm just going to call this function double.
And all it's going to do is take a number and return that number back.
But it's doubled.
So we can just return x times two.
And now what I could do is I could create a variable called num result.
And I could set it to be of the type pair two.
And it's going to store, I don't know, we'll just do a number for the key and then a number for the
value.
And then I can set it equal to the map function.
And I'll pass to the map function an array that contains numbers one, two and three.
And then I can pass a reference to the double function.
So what's going to happen here is that when this function gets called, it's going to get passed this
table one, two and three.
And it's going to see okay this is the type of number.
It's going to loop through every single number in our array and pass it to our function.
In this case that's the double function.
And it's going to return to us a doubling of that number.
So it's still going to be of the type number.
But you could have this function return whatever you like depending on what gets passed to the function.
And then after that's done, it takes that value and stores it in our result table.
And then we get it returned back to us.
So this numbers all table should be storing the doubling.
That's going to be at the first index which is one times two which is two, the doubling of two which
is four.
And then the doubling of three which is six.
And we can also do the same for like maybe we wanted to do something with a string so we could do something
called string result set it equal to a pair two.
That'll just be a string and a string.
We'll just do for this example.
And we'll set it equal to the map function.
And we'll pass an array that contains some strings like foo.
We'll do bar and then baz.
And then I'll actually create a lambda function in here.
And this function is going to take a string and it's going to return back.
And we're going to use a function from the string library called string dot rep, which means repeat
returns a string that is the concatenation of n copies of the string.
So we can just pass the string that gets passed to this lambda function and duplicate it three times.
So for example, if we pass foo, it'll be foo foo foo three times of this string and then put n there.
So now when the map function gets called on this information, we pass to it.
It's going to loop through every single string.
And it's going to call this lambda function.
And it's going to pass our string to it.
So for example it'll pass foo to this lambda function.
And then it'll take that foo string and put it in the string dot repeat function.
It's going to repeat that string three times and return it back to where the function was called, which
gets stored in the result table.
That means this string result table should be stored in a bunch of key value pairs, where foo will
be foo foo, foo, bar will be bar bar bar, and baz will be baz basbas.
So we can print out num results and string result to confirm this.
And let's go ahead and run our game.
All right, let's take a look.
Okay.
First index we get two, which is the doubling of one.
At two we get four which is the doubling of two.
And at three we get the doubling of three which is six.
And then for the strings.
There we go.
What's stored at bah is bah bah bah bah is bah bah bah and foo is foo foo foo.
So this is how you can use generics in Roblox Lua.
You remember that I always attach the code to the lecture, and I also add in some extra comments in
that code to explain things further.
If you didn't fully understand everything here in this video, and don't worry if you're not fully understanding
all of this.
But anyways, hopefully you found all the information in this lecture interesting and useful.
I don't expect you to use generics that often, but there are times when you may want to use generics
to improve the readability and cohesion of your code.
See you in the next lecture.